package com.ideaaedi.zoo.diy.artifact.seata.transaction;

import com.ideaaedi.commonds.function.NoArgConsumer;
import com.ideaaedi.commonds.function.NoArgFunction;
import com.ideaaedi.commonds.transaction.ManualTransactionSupport;
import io.seata.core.exception.TransactionException;
import io.seata.tm.api.DefaultGlobalTransaction;
import io.seata.tm.api.GlobalTransaction;
import io.seata.tm.api.GlobalTransactionContext;
import io.seata.tm.api.TransactionalExecutor;
import lombok.SneakyThrows;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;

/**
 * 手动全局事务
 *
 * @author <font size = "20" color = "#3CAA3C"><a href="https://gitee.com/JustryDeng">JustryDeng</a></font> <img
 * src="https://gitee.com/JustryDeng/shared-files/raw/master/JustryDeng/avatar.jpg" />
 * @since 1.0.0
 */
public class SeataManualTransactionSupport implements ManualTransactionSupport {
    
    /** seata分布式事务默认超时时间，与{@link DefaultGlobalTransaction#DEFAULT_GLOBAL_TX_TIMEOUT 一致}  */
    public static final int DEFAULT_GLOBAL_TX_TIMEOUT = 60000;
    
    /** seata分布式事务默认名称，与{@link DefaultGlobalTransaction#DEFAULT_GLOBAL_TX_NAME 一致}  */
    public static final String DEFAULT_GLOBAL_TX_NAME = "default";
    
    /**
     * 默认的手动本地事务支持
     */
    private volatile static Supplier<ManualTransactionSupport> defaultLocalTransactionSupportProvider;
    
    /**
     * 手动本地事务支持
     * <pre>
     * 出于以下考虑：
     * 1. 一般的，使用了@GlobalTransactional就不需要使用@Transactional了。
     *    但是当一个分支事务（本地事务）中存在多个dml时，建议使
     *    用@GlobalTransactional的同时，也使用@Transactional
     * 2. 分布式事务的概念要大于本地事务，所以如果同时还有本地事务的话，要用分布式事务包本地事务
     *
     * 所以推荐构建{@link SeataManualTransactionSupport}时，同时给予本地事务支持
     * </pre>
     */
    private final ManualTransactionSupport localTransactionSupport;
    
    /**
     * seata分布式事务名称
     */
    private final String name;
    
    /**
     * seata分布式事务超时时间
     * <pre>
     * Seata 的设计是为了保证数据一致性，防止一个全局事务长期不结束（占着锁、不释放连接等资源），
     * 导致系统出现“僵尸事务”。因此，它有一个内置的全局事务超时机制。
     *     超时计时：    当一个全局事务（@GlobalTransactional）开始时，Seata 的事务协调
     *                 器（TC）会为这个事务开始计时。
     *     默认超时时间：seata 的默认全局事务超时时间是 60秒。这意味着，如果这个全局事务在60
     *                 秒内没有完成（即没有走到 @GlobalTransactional 方法的结尾，进行全
     *                 局提交或回滚），TC 会主动干预。
     *     TC 的干预：  一旦超时，TC 会认为这个事务已经失败或僵死，它会主动发起回滚，通知所有
     *                参与者（RM）回滚它们已经执行的本地分支事务。
     * </pre>
     */
    private final int timeout;
    
    private SeataManualTransactionSupport(@Nonnull String name, int timeout, @Nullable ManualTransactionSupport localTransactionSupport) {
        this.name = Objects.requireNonNull(name, "name cannot be null.");
        this.timeout = timeout;
        this.localTransactionSupport = localTransactionSupport;
    }
    
    @Override
    @SneakyThrows
    public <P, R> R exec(Function<P, R> function, P param) {
        // 1. 获取当前全局事务实例或创建新的实例
        GlobalTransaction tx = GlobalTransactionContext.getCurrentOrCreate();
        // 2. 开启全局事务
        try {
            tx.begin(timeout, name);
        } catch (TransactionException txe) {
            // 2.1 开启失败
            throw new TransactionalExecutor.ExecutionException(tx, txe, TransactionalExecutor.Code.BeginFailure);
        }
        R rs;
        try {
            // 3. 调用业务服务（含手动本地事务）
            rs = localTransactionSupport == null ? function.apply(param) : localTransactionSupport.exec(function, param);
        } catch (Throwable ex) {
            // 业务调用本身的异常
            try {
                // 全局回滚
                tx.rollback();
                // 3.1 全局回滚成功：抛出原始业务异常
                throw ex;
            } catch (TransactionException txe) {
                // 3.2 全局回滚失败：
                throw new TransactionalExecutor.ExecutionException(tx, txe,
                        TransactionalExecutor.Code.RollbackFailure, ex);
            }
        }
        // 4. 全局提交
        try {
            tx.commit();
        } catch (TransactionException txe) {
            // 4.1 全局提交失败：
            throw new TransactionalExecutor.ExecutionException(tx, txe,
                    TransactionalExecutor.Code.CommitFailure);
        }
        return rs;
    }
    
    @Override
    @SneakyThrows
    public <R> R exec(NoArgFunction<R> function) {
        // 1. 获取当前全局事务实例或创建新的实例
        GlobalTransaction tx = GlobalTransactionContext.getCurrentOrCreate();
        // 2. 开启全局事务
        try {
            tx.begin(timeout, name);
        } catch (TransactionException txe) {
            // 2.1 开启失败
            throw new TransactionalExecutor.ExecutionException(tx, txe, TransactionalExecutor.Code.BeginFailure);
        }
        R rs;
        try {
            // 3. 调用业务服务（含手动本地事务）
            rs = localTransactionSupport == null ? function.apply() : localTransactionSupport.exec(function);
        } catch (Throwable ex) {
            // 业务调用本身的异常
            try {
                // 全局回滚
                tx.rollback();
                // 3.1 全局回滚成功：抛出原始业务异常
                throw ex;
            } catch (TransactionException txe) {
                // 3.2 全局回滚失败：
                throw new TransactionalExecutor.ExecutionException(tx, txe,
                        TransactionalExecutor.Code.RollbackFailure, ex);
            }
        }
        // 4. 全局提交
        try {
            tx.commit();
        } catch (TransactionException txe) {
            // 4.1 全局提交失败：
            throw new TransactionalExecutor.ExecutionException(tx, txe,
                    TransactionalExecutor.Code.CommitFailure);
        }
        return rs;
    }
    
    @Override
    @SneakyThrows
    public <P> void voidExec(Consumer<P> consumer, P param) {
        // 1. 获取当前全局事务实例或创建新的实例
        GlobalTransaction tx = GlobalTransactionContext.getCurrentOrCreate();
        // 2. 开启全局事务
        try {
            tx.begin(timeout, name);
        } catch (TransactionException txe) {
            // 2.1 开启失败
            throw new TransactionalExecutor.ExecutionException(tx, txe, TransactionalExecutor.Code.BeginFailure);
        }
        try {
            // 3. 调用业务服务（含手动本地事务）
            if (localTransactionSupport == null) {
                consumer.accept(param);
            } else {
                localTransactionSupport.voidExec(consumer, param);
            }
        } catch (Throwable ex) {
            // 业务调用本身的异常
            try {
                // 全局回滚
                tx.rollback();
                // 3.1 全局回滚成功：抛出原始业务异常
                throw ex;
            } catch (TransactionException txe) {
                // 3.2 全局回滚失败：
                throw new TransactionalExecutor.ExecutionException(tx, txe,
                        TransactionalExecutor.Code.RollbackFailure, ex);
            }
        }
        // 4. 全局提交
        try {
            tx.commit();
        } catch (TransactionException txe) {
            // 4.1 全局提交失败：
            throw new TransactionalExecutor.ExecutionException(tx, txe,
                    TransactionalExecutor.Code.CommitFailure);
        }
    }
    
    @Override
    @SneakyThrows
    public void voidExec(NoArgConsumer consumer) {
        // 1. 获取当前全局事务实例或创建新的实例
        GlobalTransaction tx = GlobalTransactionContext.getCurrentOrCreate();
        // 2. 开启全局事务
        try {
            tx.begin(timeout, name);
        } catch (TransactionException txe) {
            // 2.1 开启失败
            throw new TransactionalExecutor.ExecutionException(tx, txe, TransactionalExecutor.Code.BeginFailure);
        }
        try {
            // 3. 调用业务服务（含手动本地事务）
            if (localTransactionSupport == null) {
                consumer.accept();
            } else {
                localTransactionSupport.voidExec(consumer);
            }
        } catch (Throwable ex) {
            // 业务调用本身的异常
            try {
                // 全局回滚
                tx.rollback();
                // 3.1 全局回滚成功：抛出原始业务异常
                throw ex;
            } catch (TransactionException txe) {
                // 3.2 全局回滚失败：
                throw new TransactionalExecutor.ExecutionException(tx, txe,
                        TransactionalExecutor.Code.RollbackFailure, ex);
            }
        }
        // 4. 全局提交
        try {
            tx.commit();
        } catch (TransactionException txe) {
            // 4.1 全局提交失败：
            throw new TransactionalExecutor.ExecutionException(tx, txe,
                    TransactionalExecutor.Code.CommitFailure);
        }
    }
    
    /**
     * 初始化默认的本地手动事务支持
     *
     * @param localTransactionSupportProvider 提供器for本地事务手动操作器, 每次get都应获取一个新的本地事务手动操作器
     */
    public static void initLocalTransactionSupportProvider(Supplier<ManualTransactionSupport> localTransactionSupportProvider) {
        if (defaultLocalTransactionSupportProvider != null && !defaultLocalTransactionSupportProvider.equals(localTransactionSupportProvider)) {
            throw new IllegalStateException("defaultLocalTransactionSupportProvider already been initialized.");
        }
        synchronized (SeataManualTransactionSupport.class) {
            if (defaultLocalTransactionSupportProvider != null) {
                if (defaultLocalTransactionSupportProvider.equals(localTransactionSupportProvider)) {
                    return;
                }
                throw new IllegalStateException("defaultLocalTransactionSupportProvider already been initialized.");
            }
            SeataManualTransactionSupport.defaultLocalTransactionSupportProvider = localTransactionSupportProvider;
        }
    }
    
    /**
     * 创建分布式事务支持
     * <pre>
     * 出于以下考虑：
     *   有的业务逻辑本身就已经有本地事务了，那么就不需要额外再嵌套一个本地事务，只需要直接外层包一个分布式事务即可
     *
     * 示例：假设orderService.createOrder()方法本身就有本地事务了，
     *
     * {@code
     *    @Service
     *    public class OrderService {
     *
     *        @Transactional(rollbackFor = Exception.class)
     *        public OrderDetail createOrder(Param param) {
     *            ...
     *        }
     *    }
     * }
     *
     *  此时，如果你想包一个分布式事务，那么你可以
     * {@code
     *    SeataManualTransactionSupport.createDirectly().exec(() -> orderService.createOrder(param));
     * }
     *
     * 所以推荐构建{@link SeataManualTransactionSupport}时，同时给予本地事务支持
     * </pre>
     */
    public static SeataManualTransactionSupport createWithoutLts() {
        return new SeataManualTransactionSupport(DEFAULT_GLOBAL_TX_NAME, DEFAULT_GLOBAL_TX_TIMEOUT, null);
    }
    
    /**
     * 创建分布式事务支持（同时自带默认的本地事务支持）
     * @see #create(ManualTransactionSupport)
     */
    public static SeataManualTransactionSupport create() {
        ManualTransactionSupport manualTransactionSupport = Optional.ofNullable(defaultLocalTransactionSupportProvider)
                .map(Supplier::get)
                .orElseThrow(() ->
                        new IllegalArgumentException("defaultLocalTransactionSupportProvider is null. you could init it by invoke "
                                + "SeataManualTransactionSupport.initLocalTransactionSupportProvider(ManualTransactionSupport)."
                        )
                );
        
        return create(manualTransactionSupport);
    }
    
    /**
     * 创建分布式事务支持（同时自带默认的本地事务支持）
     * @param timeout 分布式事务超时时间（ms）
     *
     * @see #create(String, int, ManualTransactionSupport)
     */
    public static SeataManualTransactionSupport create(int timeout) {
        ManualTransactionSupport manualTransactionSupport = Optional.ofNullable(defaultLocalTransactionSupportProvider)
                .map(Supplier::get)
                .orElseThrow(() ->
                        new IllegalArgumentException("defaultLocalTransactionSupportProvider is null. you could init it by invoke "
                                + "SeataManualTransactionSupport.initLocalTransactionSupportProvider(ManualTransactionSupport)."
                        )
                );
        
        return create(DEFAULT_GLOBAL_TX_NAME, timeout, manualTransactionSupport);
    }
    
    /**
     * 创建分布式事务支持
     * @param localTransactionSupport 本地事务支持
     *
     * @see #create(String, int, ManualTransactionSupport)
     */
    public static SeataManualTransactionSupport create(ManualTransactionSupport localTransactionSupport) {
        return create(DEFAULT_GLOBAL_TX_NAME, DEFAULT_GLOBAL_TX_TIMEOUT, localTransactionSupport);
    }
    
    /**
     * 创建分布式事务支持
     * @param timeout 分布式事务超时时间（ms）
     * @param localTransactionSupport 本地事务支持
     *
     * @see #create(String, int, ManualTransactionSupport)
     */
    public static SeataManualTransactionSupport create(int timeout, ManualTransactionSupport localTransactionSupport) {
        return create(DEFAULT_GLOBAL_TX_NAME, timeout, localTransactionSupport);
    }
    
    /**
     * 创建分布式事务支持
     * @param name 分布式事务事务名
     * @param timeout 分布式事务超时时间（ms）
     *            <p>
     *              Seata 的设计是为了保证数据一致性，防止一个全局事务长期不结束（占着锁、不释放连接等资源），
     *              导致系统出现“僵尸事务”。因此，它有一个内置的全局事务超时机制。<br />
     *              1. 超时计时：    当一个全局事务（@GlobalTransactional）开始时，Seata 的事务协调器（TC）会为这个事务开始计时<br />
     *              2. 默认超时时间：seata 的默认全局事务超时时间是 60秒。这意味着，如果这个全局事务在60秒内没有完成（即没有走到 @GlobalTransactional 方法的结尾，进行全局提交或回滚），TC 会主动干预<br />
     *              3. TC 的干预：  一旦超时，TC 会认为这个事务已经失败或僵死，它会主动发起回滚，通知所有参与者（RM）回滚它们已经执行的本地分支事务
     *            </p>
     * @param localTransactionSupport 本地事务支持
     *            <p>
     *             出于以下考虑：<br />
     *             1. 一般的，使用了@GlobalTransactional就不需要使用@Transactional了。<br />
     *                但是当一个分支事务（本地事务）中存在多个dml时，建议使用@GlobalTransactional的同时，也使用@Transactional<br />
     *             2. 分布式事务的概念要大于本地事务，所以如果同时还有本地事务的话，要用分布式事务包本地事务<br />
     *             <br />
     *             所以推荐构建{@link SeataManualTransactionSupport}时，同时给予本地事务支持
     *            </p>
     */
    public static SeataManualTransactionSupport create(@Nonnull String name, int timeout, ManualTransactionSupport localTransactionSupport) {
        return new SeataManualTransactionSupport(name, timeout, localTransactionSupport);
    }
}
